// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
#pragma once

#include <pollux/type/simple_function_api.h>
#include <pollux/type/type.h>

namespace kumo::pollux {
    using TimeZoneKey = int16_t;

    constexpr int32_t kMillisShift = 12;
    constexpr int32_t kTimezoneMask = (1 << kMillisShift) - 1;
    // The maximum and minimum millis UTC we can represent in a
    // TimestampWithTimeZone value given the bits we have to store it.
    // We have 64 bits minus the bits for the time zone minus 1 for the sign bit.
    constexpr int64_t kMaxMillisUtc = (1L << (64 - (int64_t) kMillisShift - 1)) - 1L;
    constexpr int64_t kMinMillisUtc = (kMaxMillisUtc + 1) * -1;

    inline int64_t unpackMillisUtc(int64_t dateTimeWithTimeZone) {
        return dateTimeWithTimeZone >> kMillisShift;
    }

    inline TimeZoneKey unpackZoneKeyId(int64_t dateTimeWithTimeZone) {
        return dateTimeWithTimeZone & kTimezoneMask;
    }

    inline int64_t pack(int64_t millisUtc, TimeZoneKey timeZoneKey) {
        POLLUX_USER_CHECK(
            millisUtc <= kMaxMillisUtc && millisUtc >= kMinMillisUtc,
            "TimestampWithTimeZone overflow: {} ms",
            millisUtc);
        return (millisUtc << kMillisShift) | (timeZoneKey & kTimezoneMask);
    }

    inline int64_t pack(const Timestamp &timestamp, TimeZoneKey timeZoneKey) {
        return pack(timestamp.toMillis(), timeZoneKey);
    }

    inline Timestamp unpackTimestampUtc(int64_t dateTimeWithTimeZone) {
        return Timestamp::fromMillis(unpackMillisUtc(dateTimeWithTimeZone));
    }

    /// Represents timestamp with time zone as a number of milliseconds since epoch
    /// and time zone ID.
    class TimestampWithTimeZoneType : public BigintType {
        TimestampWithTimeZoneType() : BigintType(true) {
        }

    public:
        static const std::shared_ptr<const TimestampWithTimeZoneType> &get() {
            static const std::shared_ptr<const TimestampWithTimeZoneType> instance =
                    std::shared_ptr<TimestampWithTimeZoneType>(
                        new TimestampWithTimeZoneType());

            return instance;
        }

        bool equivalent(const Type &other) const override {
            // Pointer comparison works since this type is a singleton.
            return this == &other;
        }

        int32_t compare(const int64_t &left, const int64_t &right) const override {
            const int64_t leftUnpacked = unpackMillisUtc(left);
            const int64_t rightUnpacked = unpackMillisUtc(right);

            return leftUnpacked < rightUnpacked
                       ? -1
                       : leftUnpacked == rightUnpacked
                             ? 0
                             : 1;
        }

        uint64_t hash(const int64_t &value) const override {
            return melon::hasher<int64_t>()(unpackMillisUtc(value));
        }

        const char *name() const override {
            return "TIMESTAMP WITH TIME ZONE";
        }

        const std::vector<TypeParameter> &parameters() const override {
            static const std::vector<TypeParameter> kEmpty = {};
            return kEmpty;
        }

        std::string toString() const override {
            return name();
        }

        melon::Dynamic serialize() const override {
            melon::Dynamic obj = melon::Dynamic::object;
            obj["name"] = "Type";
            obj["type"] = name();
            return obj;
        }
    };

    inline bool isTimestampWithTimeZoneType(const TypePtr &type) {
        // Pointer comparison works since this type is a singleton.
        return TimestampWithTimeZoneType::get() == type;
    }

    inline std::shared_ptr<const TimestampWithTimeZoneType>
    TIMESTAMP_WITH_TIME_ZONE() {
        return TimestampWithTimeZoneType::get();
    }

    // Type used for function registration.
    struct TimestampWithTimezoneT {
        using type = int64_t;
        static constexpr const char *typeName = "timestamp with time zone";
    };

    using TimestampWithTimezone = CustomType<TimestampWithTimezoneT, true>;
} // namespace kumo::pollux
